.. _Функции члены:

Функции члены
==============

Хотя эта глава посвящена только структурам, большая часть информации в ней
применима и к классам.
В этой главе мы рассмотрим функции-члены структур и определим специальную функцию-член
toString(), которая используется для представления объектов в строковом
формате.
Когда определяется структура или класс, обычно вместе с ними определяется и ряд функций.
определяются вместе с ним. Примеры таких функций мы уже рассматривали в предыдущих
главах: addDuration() и перегрузка info() были написаны
специально для использования с типом TimeOfDay. В некотором смысле эти две функции
определяют интерфейс TimeOfDay.
Первым параметром и addDuration(), и info() был объект
TimeOfDay, над которым работает каждая функция. Кроме того, как и
все другие функции, которые мы видели до сих пор, обе функции были
определены на уровне модуля, вне какой-либо другой области видимости.
Концепция набора функций, определяющих интерфейс структуры, очень распространена.
распространенной. По этой причине функции, тесно связанные с типом, могут быть
могут быть определены в теле этого типа.


Определение функций-членов
--------------------------
Функции, которые определяются внутри фигурных скобок структуры, называются
функции-члены:


.. code-block:: d

    struct SomeStruct
    {
        void member_function(/* the parameters of the function */)
        {
        // ... the definition of the function ...
        }
    // ... the other members of the struct ...
    }


Доступ к функциям-членам осуществляется так же, как и к переменным-членам, отделяясь
от имени объекта точкой

.. code-block:: d

    object.member_function(arguments);


Мы уже использовали функции-члены, когда указывали stdin и stdout
при явном указании операций ввода и вывода:

.. code-block:: d

    stdin.readf(" %s", &number);
    stdout.writeln(number)


Приведенные выше readf() и writeln() являются вызовами функций-членов, работающих с объектами
объектами stdin и stdout, соответственно.
Давайте определим info() как функцию-член. Предыдущее ее определение было следующим
следующее:

.. code-block:: d

    void info(TimeOfDay time)
    {
        writef("%02s:%02s", time.hour, time.minute);
    }


Making info() a member function is not as simple as moving its definition
inside the struct. The function must be modified in two ways

Сделать info() функцией-членом не просто, перемещение её определение
внутрь структуры. Функция должна быть изменена двумя способами:

.. code-block:: d

    struct TimeOfDay
    {
        int hour;
        int minute;
        void info()
        { // (1)
            writef("%02s:%02s", hour, minute); // (2)
        }
    }


#. Функция-член не принимает объект явно в качестве параметра.
#. По этой причине она ссылается на переменные-члены просто как на час и минуту.

Это связано с тем, что функции-члены всегда вызываются на существующем объекте. Объект
объект неявно доступен для функции-члена:

.. code-block:: d

    auto time = TimeOfDay(10, 30);
    time.info();

Функция-член info() вызывается на объекте time. Члены hour и minute, на которые ссылается определение функции
соответствуют членам объекта time, а именно time.hour и
time.minute.
Приведенный выше вызов функции-члена практически эквивалентен следующему
вызову обычной функции:

.. code-block:: d

    time.info(); // member function
    info(time); // regular function (the previous definition)

Всякий раз, когда функция-член вызывается на объекте, члены объекта
становятся неявно доступными для функции:

.. code-block:: d

    auto morning = TimeOfDay(10, 0);
    auto evening = TimeOfDay(22, 0);
    morning.info();
    write('-');
    evening.info();
    writeln();

When called on morning, the hour and minute that are used inside the member
function refer to morning.hour and morning.minute. Similarly, when called on
evening, they refer to evening.hour and evening.minute:

При вызове функции с использованием объекта morning час и минута, используемые внутри члена
ссылаются на morning.hour и morning.minute. Аналогично, при вызове функции с использованием объекта
evening, они ссылаются на evening.hour и evening.minute:

.. code-block:: console

    10:00-22:00


**функция toString() для создания строковых представлений**

Мы уже обсуждали ограничения функции info() в предыдущей главе.
Есть, по крайней мере, еще одно неудобство: Хотя она выводит время в
человекочитаемом формате, печать символа '-' и завершение строки все равно
должны быть сделаны программистом явно.
Однако было бы удобнее, если бы объекты TimeOfDay можно было использовать так же
как фундаментальные типы, как в следующем коде:

.. code-block:: d

    writefln("%s-%s", morning, evening);

Помимо сокращения четырех строк кода до одной, это также позволит печатать
объектов в любой поток:

.. code-block:: d

    auto file = File("time_information", "w");
    file.writefln("%s-%s", morning, evening);

К функции-члену toString() пользовательских типов применяется особый подход: Она
вызывается автоматически для создания строковых представлений объектов.
toString() должна возвращать строковое представление объекта.
Не вдаваясь в подробности, давайте сначала посмотрим, как функция toString()
определена:

.. code-block:: d

    import std.stdio;

    struct TimeOfDay
    {
        int hour;
        int minute;
        string toString()
        {
            return "todo";
        }
    }

    void main()
    {
        auto morning = TimeOfDay(10, 0);
        auto evening = TimeOfDay(22, 0);
        writefln("%s-%s", morning, evening);
    }


toString() пока не выдает ничего значимого, но вывод показывает.
что она была вызвана writefln() дважды для двух объектов:

.. code-block:: console

    todo-todo


Также обратите внимание, что функция info() больше не нужна, ее заменяет toString().
функциональность.
Простейшей реализацией toString() будет вызов format() из модуля
format() из модуля std.string. format() работает так же, как и функции форматированного вывода
функции, например writef(). Единственное отличие заключается в том, что вместо печати переменных,
она возвращает отформатированный результат в формате строки.
Функция toString() может просто вернуть результат format() напрямую

.. code-block:: d

    // ...
    struct TimeOfDay
    {
    // ...
        string toString()
        {
            return format("%02s:%02s", hour, minute);
        }
    }

Обратите внимание, что toString() возвращает представление только этого объекта. Остальная часть
вывод обрабатывается функцией writefln(): Она вызывает функцию-член toString()
для двух объектов по отдельности, печатает символ '-' между ними и, наконец
завершает строку:


.. code-block:: d

    10:00-22:00


Определение toString(), о котором говорилось выше, не принимает никаких
параметров; оно просто создает строку и возвращает ее. Альтернативное определение
toString() принимает параметр делегата. Мы рассмотрим это определение позже в
главе "Указатели функций, делегаты и ламбды" (стр. 475).

**Пример: функция-член increment()**

Давайте определим функцию-член, которая добавляет длительность к объектам TimeOfDay.
Прежде чем это сделать, давайте сначала исправим недостаток дизайна, с которым нам пришлось столкнуться.
В главе "Структуры" (стр. 246) мы видели, что сложение двух объектов TimeOfDay
в addDuration() не является осмысленной операцией:

.. code-block:: d

    TimeOfDay addDuration(TimeOfDay start,
                          TimeOfDay duration)
    { // лишено смысла
    // ...
    }


То, что естественно добавить к моменту времени, - это длительность. Например, добавив
продолжительность поездки к времени отправления, в результате получим время прибытия.
С другой стороны, вычитание двух моментов времени - это естественная операция, в
В этом случае результатом будет длительность.
В следующей программе определена структура Duration с точностью до минуты и
функция addDuration(), которая ее использует:

.. code-block:: d

    struct Duration
    {
        int minute;
    }

    TimeOfDay addDuration(TimeOfDay start,
        Duration duration)
    {
        // Begin with a copy of start
        TimeOfDay result = start;
        // Add the duration to it
        result.minute += duration.minute;
        // Take care of overflows
        result.hour += result.minute / 60;
        result.minute %= 60;
        result.hour %= 24;
        return result;
    }

    unittest
    {
        // A trivial test
        assert(addDuration(TimeOfDay(10, 30), Duration(10)) == TimeOfDay(10, 40));
        // A time at midnight
        assert(addDuration(TimeOfDay(23, 9), Duration(51)) == TimeOfDay(0, 0));
        // A time in the next day
        assert(addDuration(TimeOfDay(17, 45), Duration(8 * 60)) == TimeOfDay(1, 45));
    }

Давайте переопределим аналогичную функцию, на этот раз как функцию-член. addDuration()
создавала в качестве результата новый объект. Давайте определим функцию-член increment()
которая будет непосредственно изменять этот объект.

.. code-block:: d

    struct Duration
    {
        int minute;
    }

    struct TimeOfDay
    {
        int hour;
        int minute;
        string toString()
        {
            return format("%02s:%02s", hour, minute);
        }

        void increment(Duration duration)
        {
            minute += duration.minute;
            hour += minute / 60;
            minute %= 60;
            hour %= 24;
        }

        unittest
        {
            auto time = TimeOfDay(10, 30);
            // A trivial test
            time.increment(Duration(10));
            assert(time == TimeOfDay(10, 40));
            // 15 hours later must be in the next day
            time.increment(Duration(15 * 60));
            assert(time == TimeOfDay(1, 40));
            // 22 hours 20 minutes later must be midnight
            time.increment(Duration(22 * 60 + 20));
            assert(time == TimeOfDay(0, 0));
        }
    }

increment() увеличивает значение объекта на указанное количество
продолжительность. В одной из следующих глав мы увидим, как перегрузка операторов в D
позволит добавлять длительность с помощью синтаксиса оператора +=.

.. code-block:: d

    time += Duration(10); // to be explained in a later chapter

Также обратите внимание, что блоки unittest можно писать и внутри определений структур,
в основном для тестирования функций-членов. По-прежнему можно перемещать такие unittest
блоки за пределы тела структуры:

.. code-block:: d

    struct TimeOfDay
    {
    // ... struct definition ...
    }
    unittest
    {
    // ... struct tests ...
    }

Упражнения
-----------

#. Добавьте функцию-член decrement() в TimeOfDay, которая должна уменьшать
время на указанную продолжительность. Как и функция increment(), она должна
переполняться на предыдущий день, если в текущем дне недостаточно времени.
Например, при вычитании 10 минут из 00:05 должно получиться 23:55.
Другими словами, реализуйте функцию decrement(), чтобы пройти следующие модульные тесты:

.. code-block:: d

   struct TimeOfDay
    {
        // ...
        void decrement(Duration duration)
        {
            // ... please implement this function ...
        }

        unittest
        {
            auto time = TimeOfDay(10, 30);
            // A trivial test
            time.decrement(Duration(12));
            assert(time == TimeOfDay(10, 18));
            // 3 days and 11 hours earlier
            time.decrement(Duration(3 * 24 * 60 + 11 * 60));
            assert(time == TimeOfDay(23, 18));
            // 23 hours and 18 minutes earlier must be midnight
            time.decrement(Duration(23 * 60 + 18));
            assert(time == TimeOfDay(0, 0));
            // 1 minute earlier
            time.decrement(Duration(1));
            assert(time == TimeOfDay(23, 59));
        }
    }


#. Преобразуйте перегрузку info() для Meeting, Meal и DailyPlan в toString()
в функции-члены. (Решения :ref:`решения Перегрузка функций<решения Перегрузка функций>`).
Вы заметите, что помимо того, что соответствующие структуры стали более
удобными, реализации функций-членов toString() будут состоять
состоять из одних строк.
